package com.liusr.template.controller;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.UUID;

/**
 * @author Liusr
 * @date 2023/2/26
 * @package com.liusr.template.controller
 * @Description: 测试Redisson分布式锁
 */
@Controller
@Slf4j
public class RedissonTestController {

	@Autowired
	RedissonClient redisson;

	@Autowired
	RedisTemplate redisTemplate;

	@ResponseBody
	@RequestMapping("/hello")
	public String hello() {
		//1、获取一把锁，只要锁的名字一样，那就是同一把锁
		RLock lock = redisson.getLock("my-lock");
		//2、加锁，默认加的锁都是30s时间
		lock.lock(); //阻塞式等待
		//1)、锁的自动续期；如果业务超长，运行期间自动给锁续上新的30s；不用担心业务时间长，锁自动过期被删掉
		//2）、加锁的业务只要运行完成，就不会给当前锁续期，即使不手动解锁，锁默认在30s后自动删除

		/**
		 * lock.lock(10, TimeUnit.SECONDS); //10秒自动解锁；解锁时间一定要大于业务操作时间
		 * 问题：如果指定解锁时间，在锁时间到了以后，不会自动续期
		 * 1、如果我们传递了锁的超时时间，就发送给redis执行脚本，进行占锁，默认超时就是我们指定的时间
		 * 2、如果我们未指定超时时间，就使用30*1000【lockWatchdogTimeout看门狗默认的时间】，只要占锁成功，就会
		 *   启动一个定时任务【重新给锁设置过期时间，新的过期时间就是看门狗的默认时间】，每隔10秒就会自动续期，续成30s
		 */
		//最佳实践
		//lock.lock(30, TimeUnit.SECONDS); 指定时间，并手动解锁
		try {
			System.out.println("加锁成功，执行业务。。。" + Thread.currentThread().getId());
			Thread.sleep(30000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			System.out.println("释放锁。。。" + Thread.currentThread().getId());
			lock.unlock();
		}
		return "hello";
	}


	//写锁保证一定能读到最新数据，修改期间，写锁是一个排他锁（互诉锁，独享锁）。读锁是一个共享锁
	//写锁没释放，读就必须等待
	//写 + 读 （写的时候进行读操作）：等待写锁释放
	//写 + 写 （写的时候进行写操作）：阻塞方式
	//读 + 写 （读的时候进行写操作）：等待读锁释放
	//读 + 读 ：相当于无锁，并发读，只会在redis中记录好，所有当前的读锁。他们都会同时加锁成功。
	//总结：只要有写的存在，就必须等待前面的锁释放。
	@ResponseBody
	@RequestMapping("/write")
	public String writeLock() {
		RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
		String s = "";
		RLock rLock = lock.writeLock();
		try {
			// 改数据加写锁，读数据加读锁
			rLock.lock();
			System.out.println("加写锁成功。。。" + Thread.currentThread().getId());
			s = UUID.randomUUID().toString();
			redisTemplate.opsForValue().set("writerValue", s);
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			rLock.unlock();
			System.out.println("释放写锁。。。。" + Thread.currentThread().getId());
		}
		return s;
	}

	@ResponseBody
	@RequestMapping("/read")
	public String readLock() {
		RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
		RLock rLock = lock.readLock();
		String s = "";
		try {
			//加读锁
			rLock.lock();
			System.out.println("加读锁成功。。。" + Thread.currentThread().getId());
			s = redisTemplate.opsForValue().get("writerValue").toString();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			rLock.unlock();
			System.out.println("释放读锁成功。。。" + Thread.currentThread().getId());
		}
		return s;
	}

	/**
	 * 车库停车(信号量)
	 * 3车位
	 * 信号量也可以用作分布式限流
	 *
	 * @return
	 */
	@ResponseBody
	@RequestMapping("/park")
	public String park() throws InterruptedException {
		RSemaphore park = redisson.getSemaphore("park");
		//park.acquire(); //获取一个信号，获取一个值，占一个车位（阻塞方式）
		boolean b = park.tryAcquire();//直接运行之后的代码，非阻塞
		if (b) {
			//执行业务
			System.out.println("执行业务....");
		}
		return "ok";
	}

	@ResponseBody
	@RequestMapping("/gogo")
	public String gogo() {
		RSemaphore park = redisson.getSemaphore("park");
		park.release(); //释放一个车位，车开走了
		return "ok";
	}
}
